package fence

import (
	"math"
	"strconv"
	"strings"
)

type Point struct {
	Lng float64 `json:"lng"` //经度(180°W-180°E) -180 - 0 - 180
	Lat float64 `json:"lat"` //纬度（90°S-90°N）-90 -0 -90
}

type Area struct {
	Points []Point //多边形坐标 顺时针方向
}

//实例化一个面
func NewGISArea(points []Point) *Area {
	area := new(Area)
	area.Points = points
	return area
}

func NewGISAreaByPoints(points string) *Area {
	pointstring := strings.Split(points, ";")
	pointArray := []Point{}
	for _, item := range pointstring {
		p := strings.Split(item, ",")
		long, _ := strconv.ParseFloat(p[0], 64)
		lat, _ := strconv.ParseFloat(p[1], 64)
		pointArray = append(pointArray, Point{
			Lng: long,
			Lat: lat,
		})
	}
	return NewGISArea(pointArray)
}

//判断一个点是否在一个面内
//如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
//@param point  指定点坐标
func (a *Area) PointInArea(point Point) bool {
	pointNum := len(a.Points) //点个数
	intersectCount := 0       //cross points count of x
	precision := 2e-10        //浮点类型计算时候与0比较时候的容差
	p1 := Point{}             //neighbour bound vertices
	p2 := Point{}
	p := point //测试点

	p1 = a.Points[0] //left vertex
	for i := 0; i < pointNum; i++ {
		if p.Lng == p1.Lng && p.Lat == p1.Lat {
			return true
		}
		p2 = a.Points[i%pointNum]
		if p.Lat < math.Min(p1.Lat, p2.Lat) || p.Lat > math.Max(p1.Lat, p2.Lat) {
			p1 = p2
			continue //next ray left point
		}

		if p.Lat > math.Min(p1.Lat, p2.Lat) && p.Lat < math.Max(p1.Lat, p2.Lat) {
			if p.Lng <= math.Max(p1.Lng, p2.Lng) { //x is before of ray
				if p1.Lat == p2.Lat && p.Lng >= math.Min(p1.Lng, p2.Lng) {
					return true
				}

				if p1.Lng == p2.Lng { //ray is vertical
					if p1.Lng == p.Lng { //overlies on a vertical ray
						return true
					} else { //before ray
						intersectCount++
					}
				} else { //cross point on the left side
					xinters := (p.Lat-p1.Lat)*(p2.Lng-p1.Lng)/(p2.Lat-p1.Lat) + p1.Lng
					if math.Abs(p.Lng-xinters) < precision {
						return true
					}

					if p.Lng < xinters { //before ray
						intersectCount++
					}
				}
			}
		} else { //special case when ray is crossing through the vertex
			if p.Lat == p2.Lat && p.Lng <= p2.Lng { //p crossing over p2
				p3 := a.Points[(i+1)%pointNum]
				if p.Lat >= math.Min(p1.Lat, p3.Lat) && p.Lat <= math.Max(p1.Lat, p3.Lat) {
					intersectCount++
				} else {
					intersectCount += 2
				}
			}
		}
		p1 = p2 //next ray left point
	}
	if intersectCount%2 == 0 { //偶数在多边形外
		return false
	} else { //奇数在多边形内
		return true
	}
}

/**
	CheckNewGISArea 检测坐标点是否在某一区域内
	Param:	areastring 	区域范围       string
		   	pointLng   	检测坐标点 经度 string
		   	pointLat   	检测坐标点 纬度 string
	Return:	bool
			false  		否
           	true  		是
*/
func CheckNewGISArea(points string, pointLng string, pointLat string) bool {
	// strpoi := strings.Split(areastring, ",")
	// var pois []Point
	// for i := 0; i < len(strpoi); i = i + 2 {
	// 	//string到float64/32
	// 	float64a, _ := strconv.ParseFloat(strpoi[i], 64)
	// 	float64b, _ := strconv.ParseFloat(strpoi[i+1], 64)
	// 	pois = append(pois, Point{float64a, float64b}) //结构体数组赋值
	// }
	// area := NewGISArea(pois)
	// var positionLng float64
	// var positionLat float64
	// positionLng, _ = strconv.ParseFloat(pointLng, 64)
	// positionLat, _ = strconv.ParseFloat(pointLat, 64)
	// point := Point{Lng: positionLng, Lat: positionLat}
	// rt := area.pointInArea(point)

	// return rt
	return true
}

// 球面距离公式：https://baike.baidu.com/item/%E7%90%83%E9%9D%A2%E8%B7%9D%E7%A6%BB%E5%85%AC%E5%BC%8F/5374455?fr=aladdin
// GeoDistance 计算地理距离，依次为两个坐标的纬度、经度、单位（默认：英里，K => 公里，N => 海里）
func GeoDistance(lng1 float64, lat1 float64, lng2 float64, lat2 float64, unit ...string) float64 {
	const PI float64 = 3.141592653589793

	radlat1 := float64(PI * lat1 / 180)
	radlat2 := float64(PI * lat2 / 180)

	theta := float64(lng1 - lng2)
	radtheta := float64(PI * theta / 180)

	dist := math.Sin(radlat1)*math.Sin(radlat2) + math.Cos(radlat1)*math.Cos(radlat2)*math.Cos(radtheta)

	if dist > 1 {
		dist = 1
	}

	dist = math.Acos(dist)
	dist = dist * 180 / PI
	dist = dist * 60 * 1.1515

	if len(unit) > 0 {
		if unit[0] == "K" {
			dist = dist * 1.609344
		} else if unit[0] == "N" {
			dist = dist * 0.8684
		}
	}

	return dist
}

func getToleranceEndpoint(a, b, c Point) Point {
	v1 := Point{b.Lng - a.Lng, b.Lat - a.Lat}
	v2 := Point{c.Lng - a.Lng, c.Lat - a.Lat}
	v := Point{v1.Lng + v2.Lng, v1.Lat + v2.Lat}
	return Point{a.Lng + v.Lng, a.Lat + v.Lat}
}

func getTolerancePoint(a, b Point, e float64) Point {
	l := GeoDistance(a.Lng, a.Lat, b.Lng, b.Lat, "K")
	v := Point{
		Lng: b.Lng - a.Lng,
		Lat: b.Lat - a.Lat,
	}
	k := e / l
	return Point{
		Lng: b.Lng + k*v.Lng,
		Lat: b.Lat + k*v.Lat,
	}
}

func GetTolerancePoints(points []Point, Tolerance float64) []Point {
	Tolerance = Tolerance / 1000 //m换算成km
	l := len(points)
	tempPoints := append(append(make([]Point, 0), points[l-1]), points...)
	ForwardTolerancePoints := []Point{}
	for i := 0; i < len(points); i++ {
		p := getTolerancePoint(tempPoints[i], tempPoints[i+1], Tolerance)
		ForwardTolerancePoints = append(ForwardTolerancePoints, p)
	}

	tempPoints = append(append(make([]Point, 0), points...), points[0])
	BackwardTolerancePoints := []Point{}
	for i := 1; i <= len(points); i++ {
		p := getTolerancePoint(tempPoints[i], tempPoints[i-1], Tolerance)
		BackwardTolerancePoints = append(BackwardTolerancePoints, p)
	}
	ps := make([]Point, 0)
	for i := 0; i < len(points); i++ {
		ps = append(ps, getToleranceEndpoint(points[i], ForwardTolerancePoints[i], BackwardTolerancePoints[i]))
	}
	return ps
}

//顺时针排序坐标
const MIN = 1e-5

func bubbleSort(arr []Point) {
	n := len(arr)
	for j := 0; j < n-1; j++ { //比较n-1轮
		for k := 0; k < n-1-j; k++ { //每轮比较n-1-j次,
			if arr[k+1].Lng < arr[k].Lng ||
				(arr[k+1].Lng == arr[k].Lng && arr[k+1].Lat > arr[k].Lat) { //从小到大
				arr[k], arr[k+1] = arr[k+1], arr[k]
			}
		}
	}
}

type tan struct {
	index int
	tan   float64
}

func tanSort(arr []tan) {
	n := len(arr)
	for j := 0; j < n-1; j++ { //比较n-1轮
		for k := 0; k < n-1-j; k++ { //每轮比较n-1-j次,
			if arr[k].tan < arr[k+1].tan { //从大到小
				arr[k], arr[k+1] = arr[k+1], arr[k]
			}
		}
	}
}

func SortPointByClockwise(points []Point) []Point {
	bubbleSort(points)
	tans := make([]tan, 0)
	for i := 1; i < len(points); i++ {
		t := tan{index: i}
		if points[i].Lng == points[0].Lng {
			t.tan = (points[i].Lat - points[0].Lat) / MIN
		} else {
			t.tan = (points[i].Lat - points[0].Lat) / (points[i].Lng - points[0].Lng)
		}
		tans = append(tans, t)
	}
	tanSort(tans)

	wisePoints := []Point{points[0]}
	for i := 0; i < len(tans); i++ {
		wisePoints = append(wisePoints, points[tans[i].index])
	}
	return wisePoints
}
